package org.test4j.mock.faking.meta;

import g_asm.org.objectweb.asm.AnnotationVisitor;
import g_asm.org.objectweb.asm.ClassReader;
import g_asm.org.objectweb.asm.ClassVisitor;
import g_asm.org.objectweb.asm.MethodVisitor;
import org.test4j.mock.faking.modifier.FakeTransformer;
import org.test4j.mock.faking.util.ClassFile;
import org.test4j.mock.faking.util.TypeUtility;

import java.util.ArrayList;
import java.util.List;

import static org.test4j.mock.faking.util.AsmConstant.acceptOptions;
import static org.test4j.mock.faking.util.AsmConstant.api_code;
import static org.test4j.mock.faking.util.ClassFile.notObjectOrProxy;
import static org.test4j.mock.faking.util.TypeDesc.T_Mock;

/**
 * 从class文件中解析出的元数据
 *
 * @author darui.wu
 */
public final class ClassMeta {
    private final Class declareToFake;
    /**
     * 类中定义的方法列表
     */
    public final List<MethodId> methods;

    public ClassMeta(Class declareToFake, Class fakeClass) {
        this.declareToFake = declareToFake;
        byte[] bytes = ClassFile.readBytesFromClassFile(fakeClass);
        ClassReader classReader = new ClassReader(bytes);
        this.methods = this.findMethods(classReader);
    }

    private List<MethodId> findMethods(ClassReader classReader) {
        List<MethodId> methods = new ArrayList<>();
        classReader.accept(new ClassVisitor(api_code) {
            @Override
            public MethodVisitor visitMethod(int access, String methodName, String paraDesc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, methodName, paraDesc, signature, exceptions);
                return new MethodVisitor(api_code, mv) {
                    @Override
                    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
                        if (!T_Mock.DESC.equals(descriptor)) {
                            return super.visitAnnotation(descriptor, visible);
                        }
                        methods.add(ClassMeta.this.buildMethodId(methodName, paraDesc));
                        return super.visitAnnotation(descriptor, visible);
                    }
                };
            }
        }, acceptOptions);
        return methods;
    }

    private MethodId buildMethodId(String name, String descriptor) {
        String methodDesc = MethodId.buildMethodDesc(name, descriptor);
        Class superClass = this.declareToFake;
        while (notObjectOrProxy(superClass)) {
            String classDesc = TypeUtility.classPath(superClass);
            Boolean isStatic = FakeTransformer.findMethodInFaked(classDesc, methodDesc);
            if (isStatic == null) {
                superClass = superClass.getSuperclass();
                continue;
            }
            return new MethodId(isStatic ? superClass : declareToFake, superClass, name, descriptor);
        }
        /**
         * 未找到实现方法, 标识为抽象方法
         */
        return new MethodId(declareToFake, null, name, descriptor);
    }
}